package de.dopichaj.labrador.genetic;

import java.util.BitSet;
import java.util.Random;

import de.dopichaj.labrador.search.merge.pattern.AbstractPattern;
import de.dopichaj.labrador.search.merge.pattern.GenericPattern;


public final class GenericPatternIndividual implements Individual<GenericPatternIndividual> {
    
    private static final String LENGTH_CHARS = "ABCDEFGH";
    private static final String REPETITION_CHARS = "01*+";
    private static final String FACTOR_CHARS = "zhkd";

    /** Size of a part in bits. */
    private static final int PART_SIZE = 7;
    private final Random rand = new Random();

    private final BitSet representation;
    private final int partCount;
    private final int bitSize;
    private final int patternCount;
    
    public GenericPatternIndividual(final int partCount, final int patternCount) {
        
        this.bitSize = patternCount * partCount * PART_SIZE;
        this.representation = new BitSet(bitSize);
        this.partCount = partCount;
        this.patternCount = patternCount;
    }
    
    public GenericPatternIndividual(final BitSet representation,
        final int partCount, final int patternCount) {
        
        this.representation = representation;
        this.partCount = partCount;
        this.patternCount = patternCount;
        this.bitSize = patternCount * partCount * PART_SIZE;
    }
    
    public GenericPatternIndividual(GenericPatternIndividual other) {
        this.representation = (BitSet)other.representation.clone();
        this.partCount = other.partCount;
        this.bitSize = other.bitSize;
        this.patternCount = other.patternCount;
    }

    public GenericPatternIndividual(String... patterns) {
        this(patterns[0].length() / 3, patterns.length);

        // for each pattern
        for (int patternNr = 0; patternNr < patternCount; patternNr++) {
            final String pattern = patterns[patternNr];
            
            // make sure all patterns have the same length
            if (pattern.length() != partCount * 3) {
                throw new IllegalArgumentException("Pattern " + patternNr +
                    " has illegal length: is " + pattern.length() + ", should be " +
                    partCount * 3);
            }
            
            // generate the representation
            final int offset = patternNr * partCount * PART_SIZE;
            for (int i = 0; i < partCount; i++) {
                final char len = pattern.charAt(3 * i);
                final int lenIndex = LENGTH_CHARS.indexOf(len);
                if (lenIndex == -1) {
                    throw new IllegalArgumentException
                        ("Illegal pattern length char in part " + i);
                }
                final char rep = pattern.charAt(3 * i + 1);
                int repIndex = REPETITION_CHARS.indexOf(rep);
                if (repIndex == -1) {
                    throw new IllegalArgumentException
                        ("Illegal pattern repetition char in part " + i);
                }
                final char fac = pattern.charAt(3 * i + 2);
                int facIndex = FACTOR_CHARS.indexOf(fac);
                if (facIndex == -1) {
                    throw new IllegalArgumentException
                        ("Illegal pattern factor char in part " + i);
                }
                
                final int partPos = offset + PART_SIZE * i;
                setValueAt(partPos, 3, lenIndex);
                setValueAt(partPos + 3, 2, repIndex);
                setValueAt(partPos + 5, 2, facIndex);
            }
        }
    }

    public String[] getPatternStrings() {
        final String[] patternStrings = new String[patternCount];
        
        for (int patternNr = 0; patternNr < patternCount; patternNr++) {
            final StringBuilder builder = new StringBuilder();
            
            final int offset = patternNr * partCount * PART_SIZE;
            for (int partNr = 0; partNr < partCount; partNr++) {
                final int start = offset + PART_SIZE * partNr;
                char repetitionChar = getRepetitionChar(start + 3);
                if (repetitionChar != '0') {
                    builder.append(getLengthChar(start));
                    builder.append(repetitionChar);
                    builder.append(getFactorChar(start + 5));
                }
            }
            // set parent's repetition count to 1
            if (builder.length() > 1) {
                builder.setCharAt(1, '1');
            }

            patternStrings[patternNr] = builder.toString();
        }
        
        return patternStrings;
    }

    public void mutate() {
        representation.flip(rand.nextInt(bitSize));
    }
    
    public void crossWith(final GenericPatternIndividual individual) {
        int cutLeft = rand.nextInt(partCount * patternCount);
        int cutRight = rand.nextInt(partCount * patternCount);
        if (cutLeft > cutRight) {
            int i = cutLeft;
            cutLeft = cutRight;
            cutRight = i;
        }
        
        for (int i = cutLeft * PART_SIZE; i <= cutRight * PART_SIZE; i++) {
            representation.set(i, individual.representation.get(i));
        }
    }

    private char getFactorChar(int i) {
        return FACTOR_CHARS.charAt(getValueAt(i, 2));
    }

    private char getRepetitionChar(int i) {
        return REPETITION_CHARS.charAt(getValueAt(i, 2));
    }

    private char getLengthChar(int i) {
        return LENGTH_CHARS.charAt(getValueAt(i, 3));
    }

    /**
     * Get the value from the <code>bitCount</code> bits at position <code>i</code>.
     */
    private int getValueAt(int i, int bitCount) {
        int mask = 1;
        int value = 0;
        for (int j = i; j < i + bitCount; j++) {
            value += representation.get(j) ? mask : 0;
            mask <<= 1;
        }
        return value;
    }
    
    private void setValueAt(int i, int bitCount, int value) {
        int mask = 1;
        for (int j = i; j < i + bitCount; j++) {
            representation.set(j, (value & mask) != 0);
            mask <<= 1;
        }
    }

    @Override
    public String toString() {
        return "Individual " + representation + " (" + getGenomeString() + ")";
    }
    
    public static GenericPatternIndividual makeRandomIndividual(
        final int partCount, final int patternCount) {
        final BitSet bits = new BitSet();
        
        for (int i = 0; i < PART_SIZE * patternCount * partCount; i++) {
            bits.set(i, Math.random() <= 0.5);
        }
        
        return new GenericPatternIndividual(bits, partCount, patternCount);
    }

    public AbstractPattern getPattern() {
        return new GenericPattern(getPatternStrings());
   }

    public String getGenomeString() {
        final StringBuilder builder = new StringBuilder();
        for (final String patternString : getPatternStrings()) {
            builder.append(patternString).append(" ");
        }
        return builder.toString();
    }

    public GenericPatternIndividual copy() {
        return new GenericPatternIndividual(this);
    }

    public GenericPatternIndividual getImplementation() {
        return this;
    }
}
/*
Copyright (c) 2007 Philipp Dopichaj

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/